﻿// <copyright file="Piwik.cs">
// Copyright (c) 2012 bauer-martin.com
// </copyright>
// <author>Martin Bauer</author>
// <email>piwik@bauer-martin.com.com</email>
// <date>2012-11-02</date>
// <summary>MSAF Analytics Service for Piwik</summary>

using System;
using System.ComponentModel.Composition;
using System.Globalization;
using System.IO.IsolatedStorage;
using System.Net;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interactivity;
using System.Windows.Media.Imaging;
using Microsoft.WebAnalytics;
using Microsoft.WebAnalytics.Data;

namespace WP8
{
    public class PiwikWebAnalytics : Behavior<Panel>
    {
        /// <summary>
        /// Create new PiwikWebAnalytics
        /// </summary>
        public PiwikWebAnalytics()
        {
            // Default Api Version
            ApiVersion = "1";
        }

        /// <summary>
        /// This is enabled if on an HTML Page
        /// </summary>
        protected override void OnAttached()
        {
            if (ReadSessionData())
                WriteSessionData();

            // base
            base.OnAttached();

            CompositionInitializerEx.SatisfyImports(this);
            Events.TrackEvent += OnTrackEvent;

            // initialize image
            _image = new Image();
            _image.ImageFailed += OnImageFailed;

            // add image
            AssociatedObject.Children.Add(_image);
            AssociatedObject.Unloaded += AssociatedObjectUnloaded;
        }

        /// <summary>
        /// Remove the page tag
        /// </summary>
        protected override void OnDetaching()
        {
            base.OnDetaching();
            DetachEvents();
        }

        #region Events

        /// <summary>
        /// Track Event
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnTrackEvent(object sender, AnalyticsEventArgs e)
        {
            if (!ShouldTrackEvent(e))
                return;

            switch (e.AnalyticsEvent.Name)
            {
                case "Exiting":
                    // Save Session Data
                    WriteSessionData();
                    break;
                case "Exited":
                    break;
                default:
                    UpdateImage(e);
                    break;
            }
        }

        /// <summary>
        /// Error Image Navigation
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnImageFailed(object sender, ExceptionRoutedEventArgs e)
        {
        }

        /// <summary>
        /// On AssociatedObject is unloaded
        /// Remove event handlers and behavior
        /// </summary>
        /// <param name="sender">the associated object that the behavior is attached to</param>
        /// <param name="routedEventArgs">the routed event arguments</param>
        private void AssociatedObjectUnloaded(object sender, RoutedEventArgs routedEventArgs)
        {
            AssociatedObject.Unloaded -= AssociatedObjectUnloaded;
            DetachEvents();
        }

        #endregion
        #region Session Data

        /// <summary>
        /// Read Session Data
        /// </summary>
        /// <returns></returns>
        private bool ReadSessionData()
        {
            try
            {
                // read unique User Id
                Settings.TryGetValue(PiwikSettingsUserId, out _userId);
                // read user first fisit time
                Settings.TryGetValue(PiwikSettingsIdTs, out _userIdTs);
                // read user visit count
                Settings.TryGetValue(PiwikSettingsIdVc, out _userIdVc);
                // increase visit count
                _userIdVc++;
                // Enalbe Application Storage
                ApplicationStorageEnabled = true;
            }
            catch (IsolatedStorageException)
            {
                ApplicationStorageEnabled = false;

                // build user id
                _userId = BuilderUserId();
                // user first visit time
                _userIdTs = DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1));
                // user visit count
                _userIdVc = 1;
            }

            return true;
        }

        /// <summary>
        /// Save Session Data
        /// </summary>
        private void WriteSessionData()
        {
            // no storage, cancle
            if (!ApplicationStorageEnabled)
                return;

            // Set values
            Settings[PiwikSettingsUserId] = _userId;
            Settings[PiwikSettingsIdTs] = _userIdTs;
            // Save
            Settings.Save();
        }

        #endregion
        #region Helpers

        /// <summary>
        /// Returns whether the specific event should be tracked
        /// </summary>
        /// <param name="e">the event arguments</param>
        /// <returns>
        /// true if the event should be tracked
        /// </returns>
        private static bool ShouldTrackEvent(AnalyticsEventArgs e)
        {
            // no page view
            if (e.AnalyticsEvent.HitType != HitType.PageView)
                return false;

            // Url is set
            return !String.IsNullOrEmpty(e.AnalyticsEvent.NavigationState);
        }

        /// <summary>
        /// Detach events and the image
        /// </summary>
        private void DetachEvents()
        {
            // Track Event
            if (Events != null)
            {
                Events.TrackEvent -= OnTrackEvent;
                Events = null;
            }

            // no image set
            if (_image == null)
                return;

            // image on failed
            _image.ImageFailed -= OnImageFailed;
            // remove image from associated object
            AssociatedObject.Children.Remove(_image);
            // clear image
            _image = null;
        }

        /// <summary>
        /// Update the image of the Page tag
        /// </summary>
        /// <param name="e">the analytics event arguments</param>
        private void UpdateImage(AnalyticsEventArgs e)
        {
            // set new image uri
            _image.Source = new BitmapImage(BuildUrl(e.AnalyticsEvent));
        }

        /// <summary>
        /// Build new Image Uri to Piwik Analytics
        /// </summary>
        /// <param name="logEvent"></param>
        /// <returns>the analytics event</returns>
        private Uri BuildUrl(AnalyticsEvent logEvent)
        {
            var builder = new StringBuilder();

            // build base url path
            builder.AppendFormat(CultureInfo.InvariantCulture, "{0}piwik.php?idsite={1}&rec=1&apiv={2}", ServiceUrl, IdSite, ApiVersion);

            // set user id
            if (!String.IsNullOrEmpty(_userId))
                builder.AppendFormat(CultureInfo.InvariantCulture, "&_id={0}", _userId);
            // set first visit time
            if (_userIdTs.TotalSeconds > 0)
                builder.AppendFormat(CultureInfo.InvariantCulture, "&_idts={0}", (int)_userIdTs.TotalSeconds);

            // action name
            builder.AppendFormat(CultureInfo.InvariantCulture, "&action_name={0}", HttpUtility.UrlEncode(logEvent.ObjectName));

            // user visit count
            builder.AppendFormat(CultureInfo.InvariantCulture, "&_idvc={0}", _userIdVc);

            // set url to track
            if (!String.IsNullOrEmpty(logEvent.NavigationState))
                builder.AppendFormat(CultureInfo.InvariantCulture, "&url={0}", HttpUtility.UrlEncode(String.Format("http://{0}{1}", logEvent.AppName, logEvent.NavigationState)));
            // set ref url
            if (!String.IsNullOrEmpty(logEvent.Referrer))
                builder.AppendFormat(CultureInfo.InvariantCulture, "&urlref={0}", HttpUtility.UrlEncode(String.Format("http://{0}{1}", logEvent.AppName, logEvent.Referrer)));

            // set resolution of device
            var resolution = DisplayResolution;
            builder.AppendFormat(CultureInfo.InvariantCulture, "&res={0}x{1}", resolution.Width, resolution.Height);

            // return new url to piwik track image
            return new Uri(builder.ToString(), UriKind.Absolute);
        }

        /// <summary>
        /// Build new User Id
        /// </summary>
        /// <returns>new unique user id</returns>
        private string BuilderUserId()
        {
            var chars = new[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };

            var builder = new StringBuilder();
            var rand = new Random();

            for (int i = 0; i < 16; i++)
                builder.Append(chars[rand.Next(0, 15)]);

            return builder.ToString();
        }

        /// <summary>
        /// Get Property
        /// </summary>
        /// <param name="instance"></param>
        /// <param name="name"></param>
        /// <returns></returns>
        private static object GetProperty(object instance, string name)
        {
            return instance.GetType().GetProperty(name).GetGetMethod().Invoke(instance, null);
        } 

        #endregion
        #region Properties

        /// <summary>
        /// Gets or sets the Web Analytics events
        /// </summary>
        [Import]
        public WebAnalyticsEvents Events { get; set; }

        /// <summary>
        /// Your Piwik Page Id
        /// </summary>
        public int IdSite { set; get; }

        /// <summary>
        /// Url of your Piwik Service
        /// </summary>
        public Uri ServiceUrl { set; get; }

        /// <summary>
        /// Api Version
        /// </summary>
        public string ApiVersion { set; get; }

        /// <summary>
        /// The native Image
        /// </summary>
        private Image _image;

        /// <summary>
        /// User Id
        /// </summary>
        private string _userId;

        /// <summary>
        /// Users first Visit
        /// </summary>
        private TimeSpan _userIdTs;

        /// <summary>
        /// User Visit Count
        /// </summary>
        private int _userIdVc;

        /// <summary>
        /// Gets or sets a value indicating whether application storage is enabled
        /// </summary>
        private bool ApplicationStorageEnabled { get; set; }

        /// <summary>
        /// Gets the Application Settingsa
        /// </summary>
        /// <exception cref="T:System.IO.IsolatedStorage.IsolatedStorageException">If the Isolated Storage is disabled</exception>
        private IsolatedStorageSettings Settings
        {
            get { return IsolatedStorageSettings.ApplicationSettings; }
        }

        /// <summary>
        /// Get Display Resolution if defice
        /// </summary>
        public static Size DisplayResolution
        {
            get
            {
                // if windows phone 7, always return 480x800
                if (Environment.OSVersion.Version.Major < 8)
                    return new Size(480, 800);

                // get scale factor
                int scaleFactor = (int)GetProperty(Application.Current.Host.Content, "ScaleFactor");
                // based on scale factor create resolution
                switch (scaleFactor)
                {
                    case 100:
                        return new Size(480, 800);
                    case 150:
                        return new Size(720, 1280);
                    case 160:
                        return new Size(768, 1280);
                    default:
                        return new Size(480, 800);
                }
            }
        }

        #endregion
        #region Const

        /// <summary>
        /// settings name for user id
        /// </summary>
        private const string PiwikSettingsUserId = "Piwik.Settings.UserId";

        /// <summary>
        /// settings name for first visit
        /// </summary>
        private const string PiwikSettingsIdTs = "Piwik.Settings.IdTs";

        /// <summary>
        /// settings name for visit count
        /// </summary>
        private const string PiwikSettingsIdVc = "Piwik.Settings.IdVc";

        #endregion
    }
}